package com.ymt.bpm.service.engine.bpd.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.ymt.bpm.appboot.env.Caches;
import com.ymt.bpm.dao.MDao;
import com.ymt.bpm.dao.Mapper;
import com.ymt.bpm.engine.EngineConst;
import com.ymt.bpm.engine.bpmn.BpmnRender;
import com.ymt.bpm.model.*;
import com.ymt.bpm.model.Const.Oplog;
import com.ymt.bpm.service.engine.bpd.BpdRefService;
import com.ymt.bpm.service.engine.bpd.BpdService;
import com.ymt.bpm.service.platform.cache.CacheService;
import com.ymt.bpm.service.platform.log.OpLogService;
import com.ymt.bpm.util.Const;
import com.ymt.bpm.util.DateUtil;
import com.ymt.bpm.util.DhxUtil;
import org.activiti.engine.RepositoryService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayInputStream;
import java.util.*;

/**
 * Created by Johnny on 2017/3/23.
 */
@Service
public class BpdServiceImpl implements BpdService {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private MDao mDao;

    @Autowired
    private BpdRefService brs;

    @Autowired
    private OpLogService oplog;

    @Autowired
    private CacheService cacheService;

    /**
     * Activiti repository service
     */
    @Autowired
    private RepositoryService actRs;

    @Override
    public JSONObject query(String tenantId, String category,
                            String status, String search,
                            String sort, String order,
                            int offset, int limit,
                            String lang, int timezoneOffset) {
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("category", category);
        paramMap.put("status", status);
        paramMap.put("search", search);
        paramMap.put("sort", sort);
        paramMap.put("order", order);
        PageHelper.offsetPage(offset, limit);
        List<PBpd> list = mDao.selectList(Mapper.PBpdMapper, "query", paramMap);
        PageInfo pageInfo = new PageInfo(list);
        JSONObject rtn = new JSONObject();
        JSONArray rows = new JSONArray();
        if (list!=null && list.size()>0) {
            for (PBpd bpd : list) {
                JSONObject row = DhxUtil.toFastJsonWithDate(bpd, lang, timezoneOffset);
                row.put("_ITEM_NO", ++offset);
                rows.add(row);
            }
        }
        rtn.put("rows", rows);
        rtn.put("total", pageInfo.getTotal());
        return rtn;
    }

    @Override
    public JSONObject get(String tenantId, String bpdId, String lang, int timezoneOffset) {
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("bpdId", bpdId);
        PBpd bpd = mDao.selectOne(Mapper.PBpdMapper, "selectByPrimaryKey", paramMap);
        return DhxUtil.toFastJsonWithDate(bpd, lang, timezoneOffset);
    }

    @Override
    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public JSONObject save(String tenantId, String loginName, String displayName, PBpdWithBLOBs bpd) {
        int rs = 0;
        bpd.setTenantId(tenantId);
        if (bpd.getBpdId()==null || bpd.getBpdId().length()==0) {
            bpd.setBpdId("FLOW-"+ UUID.randomUUID().toString());
            bpd.setStatus("00");
            bpd.setCreator(loginName);
            bpd.setCreatorName(displayName);
            bpd.setCreateTime(DateUtil.getGMTDate());
            rs = mDao.insert(Mapper.PBpdMapper, "insertSelective", bpd);

            //日志
            oplog.bizlog(tenantId, loginName, displayName, null, Oplog.OP.CREATE, Oplog.CODE.PROCESS_BPD, bpd.getBpdName());
        } else {
            bpd.setUpdator(loginName);
            bpd.setUpdatorName(displayName);
            bpd.setUpdateTime(DateUtil.getGMTDate());
            rs = mDao.update(Mapper.PBpdMapper, "updateByPrimaryKeySelective", bpd);

            //@CacheEvict
            cacheService.remove(Caches.BPD, cacheService.asKey(Caches.BpdPrefix.BASIC_BEAN.name(), tenantId, bpd.getBpdId()));

            //日志
            oplog.bizlog(tenantId, loginName, displayName, null, Oplog.OP.UPDATE, Oplog.CODE.PROCESS_BPD, bpd.getBpdName());
        }

        JSONObject rtn = new JSONObject();
        rtn.put("result", rs);
        rtn.put("bpdId", bpd.getBpdId());
        return rtn;
    }

    @Override
    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public int saveModel(String tenantId, String loginName, String displayName, String bpd) {
        JSONObject bpdjo = JSONObject.parseObject(bpd);
        PBpdWithBLOBs record = new PBpdWithBLOBs();
        record.setBpdId(bpdjo.getString("bpdId"));
        record.setBpmnConfig(bpdjo.getJSONObject("bpmnConfig").toJSONString());
        record.setOtherConfig(bpdjo.getJSONObject("otherConfig").toJSONString());
        record.setBpmn(bpdjo.getString("bpmn"));
        record.setSvg(bpdjo.getString("svg"));
        record.setUpdateTime(DateUtil.getGMTDate());
        record.setUpdator(loginName);
        record.setUpdatorName(displayName);
        int rs = mDao.update(Mapper.PBpdMapper, "updateByPrimaryKeySelective", record);

        //save refs
        rs += brs.saveRefs(tenantId, record.getBpdId(), "-1", record.getBpmn(), record.getOtherConfig());

        //日志
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("bpdId", record.getBpdId());
        PBpd bpd1 = mDao.selectOne(Mapper.PBpdMapper, "selectByPrimaryKey", paramMap);
        oplog.bizlog(tenantId, loginName, displayName, null, Oplog.OP.UPDATE, Oplog.CODE.PROCESS_BPD_MODEL, bpd1.getBpdName());

        return rs;
    }

    @Override
    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public JSONObject clone(String tenantId, String loginName, String displayName, PBpdWithBLOBs bpd) {
        int rs = 0;
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("bpdId", bpd.getBpdId());
        PBpdWithBLOBs bpdForInsert = mDao.selectOne(Mapper.PBpdMapper, "selectByPrimaryKey", paramMap);
        String orginName = bpdForInsert.getBpdName();

        bpdForInsert.setBpdId("FLOW-"+ UUID.randomUUID().toString());
        bpdForInsert.setBpdName(bpd.getBpdName());
        bpdForInsert.setBpdDesc(bpd.getBpdDesc());
        bpdForInsert.setStatus("00");
        bpdForInsert.setCreator(loginName);
        bpdForInsert.setCreatorName(displayName);
        bpdForInsert.setCreateTime(DateUtil.getGMTDate());
        bpdForInsert.setUpdator(null);
        bpdForInsert.setUpdatorName(null);
        bpdForInsert.setUpdateTime(null);
        rs = mDao.insert(Mapper.PBpdMapper, "insertSelective", bpdForInsert);

        //记录操作日志

        oplog.bizlog(tenantId, loginName, displayName, null, Oplog.OP.CLONE_BPD, Oplog.CODE.PROCESS_BPD, orginName + " -> " +bpdForInsert.getBpdName());

        JSONObject rtn = new JSONObject();
        rtn.put("result", rs);
        rtn.put("bpdId", bpdForInsert.getBpdId());
        return rtn;
    }

    @Override
    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public JSONObject invalidate(String tenantId, String loginName, String displayName, String bpdId) {
        return null;
    }

    @Override
    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public JSONObject delete(String tenantId, String loginName, String displayName, String bpdId) {
        JSONObject rtn = new JSONObject();

        //check reference
        JSONArray refs = brs.referencedBy(tenantId, bpdId, "-1");
        if (refs!=null) {
            rtn.put("refs", refs);
            return rtn;
        }

        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("bpdId", bpdId);

        PBpd bpdToDel = mDao.selectOne(Mapper.PBpdMapper, "selectByPrimaryKey", paramMap);

        //check instances
        int countPi = mDao.selectOne(Mapper.PBpdMapper, "countPi", paramMap);
        if (countPi>0) {
            rtn.put("pi", countPi);
            return rtn;
        }

        int rs = mDao.delete(Mapper.PBpdMapper, "deleteByPrimaryKey", paramMap);

        //记录操作日志
        oplog.bizlog(tenantId, loginName, displayName, null, Oplog.OP.DELETE, Oplog.CODE.PROCESS_BPD, bpdToDel.getBpdName());

        rtn.put("result", rs);
        return rtn;
    }

    @Override
    public String checkName(String tenantId, String bpdId, String bpdName) {
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("bpdId", bpdId);
        paramMap.put("bpdName", bpdName);
        int count = mDao.selectOne(Mapper.PBpdMapper, "countByBpdName", paramMap);
        return count>0 ? Boolean.FALSE.toString() : Boolean.TRUE.toString();
    }

    @Override
    public JSONObject getVersions(String tenantId, String bpdId,
                                  String search, String sort, String order,
                                  int offset, int limit,
                                  String lang, int timezoneOffset) {
        Map<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put(Const.TENANT_ID, tenantId);
        paramMap.put("bpdId", bpdId);
        paramMap.put("search", search);
        paramMap.put("sort", sort);
        paramMap.put("order", order);
        PageHelper.offsetPage(offset, limit);
        List<PBpdVersion> list = mDao.selectList(Mapper.PBpdVersionMapper, "query", paramMap);
        PageInfo pageInfo = new PageInfo(list);
        JSONObject rtn = new JSONObject();
        JSONArray rows = new JSONArray();
        if (list!=null && list.size()>0) {
            for (PBpdVersion version : list) {
                JSONObject row = DhxUtil.toFastJsonWithDate(version, lang, timezoneOffset);
                row.put("_ITEM_NO", ++offset);
                rows.add(row);
            }
        }
        rtn.put("rows", rows);
        rtn.put("total", pageInfo.getTotal());
        return rtn;
    }

    @Override
    public int getCurrentVersionId(String tenantId, String bpdId) {
        Map<String, Object> bpdKeymap = new HashMap<String, Object>();
        bpdKeymap.put(Const.TENANT_ID, tenantId);
        bpdKeymap.put("bpdId", bpdId);
        //从引擎获取最新版本号
        int versionid = mDao.selectOne(Mapper.PBpdVersionMapper, "getCurrentVersionid", bpdKeymap);
        return versionid;
    }

    /**
     * deploy a bpd
     * @param tenantId
     * @param loginName
     * @param displayName
     * @param bpdId
     * @return
     */
    @Override
    @Transactional(EngineConst.TRANSACTION_MANAGER)
    public JSONObject deploy(String tenantId, String loginName, String displayName, String bpdId) {
        JSONObject rtn = new JSONObject();
        int rs = 0;
        try {
            Map<String, Object> bpdKeymap = new HashMap<String, Object>();
            bpdKeymap.put(Const.TENANT_ID, tenantId);
            bpdKeymap.put("bpdId", bpdId);
            //从引擎获取最新版本号
            PBpdWithBLOBs bpd = mDao.selectOne(Mapper.PBpdMapper, "selectByPrimaryKey", bpdKeymap); // 当前最新版
            log.debug("deploy get bpd=" + bpd);

            // 获取部署前最大版本号并加1
            int currentVid = mDao.selectOne(Mapper.ActivitiMapper, "selectMaxVersionByKey", bpdKeymap);
            int newVersionId = currentVid + 1;

            // 转换为可部署bpmn xml
            BpmnRender render = new BpmnRender(bpdId, bpd.getBpdName(), newVersionId, bpd.getBpmn(), null, bpd.getBpmnConfig());
            String bpmn = render.getTargetDocumentString();
            log.debug("deploying:\n" + bpmn);
            // BpmnModel model = new DefaultBpmnParseFactory().createBpmnParse(new BpmnParser()).sourceInputStream(new
            // ByteArrayInputStream(bpmn.getBytes(Const.ENCODING))).execute().getBpmnModel();
            // rs.createDeployment().name(bpd.getFlowname()).addBpmnModel(bpdid, model).deploy();
            actRs.createDeployment()
                    .tenantId(tenantId) //set tenantId
                    // .category("流程类别")
                    .name(bpd.getBpdName()).addInputStream(bpdId + ".bpmn", new ByteArrayInputStream(bpmn.getBytes(Const.ENCODING)))
                    // .addString(bpd.getFlowname(), bpmn)
                    .deploy();

            // 如果原来为未部署状态，设置状态为启动，否则状态保持不变
            if ("00".equals(bpd.getStatus())) {
                bpd.setStatus("10");
            }
            rs += mDao.update(Mapper.PBpdMapper, "updateByPrimaryKeySelective", bpd);

            //设置部署前的当前版本为0
            rs += mDao.update(Mapper.PBpdVersionMapper, "setNoCurrent", bpdKeymap);

            // 需要另存并部署的新版本
            PBpdVersionWithBLOBs version = new PBpdVersionWithBLOBs();
            if (tenantId!=null && StringUtils.isNumeric(tenantId)) {
                version.setTenantId(Integer.parseInt(tenantId));
            }
            version.setBpdId(bpd.getBpdId());
            version.setVersionid(newVersionId);
            // version.setVersionname(bpd.getBpdName()+" Version:"+newVersionId);
            // version.setVersiondesc(bpd.getBpdName()+" Version:"+newVersionId);
            version.setCreator(loginName);
            version.setCreatorName(displayName);
            version.setCreateTime(DateUtil.getGMTDate());
            version.setBpmn(bpd.getBpmn());
            version.setBpmnConfig(bpd.getBpmnConfig());
            version.setOtherConfig(bpd.getOtherConfig());
            version.setSvg(bpd.getSvg());
            version.setIsCurrent(1);
            // 保存版本
            rs += mDao.insert(Mapper.PBpdVersionMapper, "insert", version);

            //保存业务单号生成规则
            PBpdSeq bseq = mDao.selectOne(Mapper.PBpdSeqMapper, "selectByPrimaryKey", bpdKeymap);
            if (bseq==null) {
                bseq = new PBpdSeq();
                if (tenantId!=null && StringUtils.isNumeric(tenantId)) {
                    bseq.setTenantId(Integer.parseInt(tenantId));
                }
                bseq.setBpdId(bpdId);
                bseq.setCurrentValue(1);
                bseq.setIncrement(1);
                if (bpd.getBizNoPattern()!=null && bpd.getBizNoPattern().length()>0) {
                    bseq.setPattern(bpd.getBizNoPattern());
                    if (bpd.getBizNoPattern().indexOf("{YYYY}")>=0) {
                        bseq.setResetby("Y");
                    } else if (bpd.getBizNoPattern().indexOf("{MM}")>=0) {
                        bseq.setResetby("M");
                    } else if (bpd.getBizNoPattern().indexOf("{DD}")>=0) {
                        bseq.setResetby("D");
                    } else {
                        bseq.setResetby("N");
                    }
                } else {
                    bseq.setPattern("{SEQ8}");
                    bseq.setResetby("N");
                }
                rs += mDao.insert(Mapper.PBpdSeqMapper, "insert", bseq);
            } else {
                //部署后不能再修改
            }

            //记录日志
            oplog.bizlog(tenantId, loginName, displayName, null, Oplog.OP.DEPLOY, Oplog.CODE.PROCESS_BPD, bpd.getBpdName());

            log.debug("Process deployed. id=" + bpdId + " name=" + bpd.getBpdName());
            rtn.put(Const.AJAX_RESULT, rs);
        } catch (Exception e) {
            e.printStackTrace();
            rtn.put(Const.AJAX_RESULT, rs);
        }
        return rtn;
    }

}
